using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;

namespace GuavaData
{
    public class Adorn : SemanticTransform
    {
        List<string> tables = new List<string>();
        string newColumn;
        Function adorner;
        bool insertOnly;
        object[] now;

        public Adorn(IEnumerable<string> tables, string newColumn, Function adorner, bool insertOnly)
        {
            // Validate the adorner - must have 0 inputs and 1 output
            if (!(ValidateFunction(adorner))) throw new TransformException("Cannot use function " + adorner.GetType().Name + ".  Adorn transforms only work with functions with no inputs and one output.");

            this.tables.AddRange(tables);
            this.newColumn = newColumn;
            this.adorner = adorner;
            this.insertOnly = insertOnly;
        }

        public bool ValidateFunction(Function adorner)
        {
            if (adorner.inputs.Count != 0) return false;
            if (adorner.outputs.Count != 1) return false;
            return true;
        }

        public override void BuildUnits(string table, List<SemanticUnit> ls)
        {
            if (this.tables.Contains(table))
            {
                ls.Add(new AddColumnUnit(this.newColumn, this.adorner.outputs[0]));
            }
        }

        public override System.Data.DataSet Process(Transaction o)
        {
            now = adorner.apply();
            return base.Process(o);
        }

        internal override void Expand(AddColumn ac, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(ac.Table)))
                return;

            if (ac.Column == this.newColumn)
                throw new DDLSideEffectException("Adorn operator already tries to add a column named " + this.newColumn + ".");
        }

        internal override void Expand(AddTable at, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(at.Table)))
                return;

            if (at.Columns.Contains(this.newColumn))
                throw new Exception("Table " + at.Table + " already contains a column named " + this.newColumn + " and cannot be adorned.");

            at.AddColumn(this.newColumn, this.adorner.outputs[0], false);
        }

        internal override void Expand(AlterColumnRename acr, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(acr.Table)))
                return;

            if (acr.NewName == this.newColumn)
                throw new DDLSideEffectException("Adorn operator already tries to add a column named " + this.newColumn + ".");

            // Just in case we get a rename on the adorn column, update the internals
            if (acr.OldName == this.newColumn)
                this.newColumn = acr.NewName;
        }

        internal override void Expand(AlterTableRename atr, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(atr.OldName)))
            {
                if (this.tables.Contains(atr.NewName))
                    throw new DDLSideEffectException("Adorn operator already tries to add a column named " + this.newColumn + ".");

                return;
            }

            // Adjust internals
            this.tables.Remove(atr.OldName);
            if (!(this.tables.Contains(atr.NewName)))
                this.tables.Add(atr.NewName);
        }
        
        internal override void Expand(Insert i, Command c)
        {
            // If not one of the tables we are looking for, then punt
            if (!(this.tables.Contains(i.Table)))
                return;
            
            i.Child.Visit(this, c);

            // Attach the current value of the function to the insert statement
            i.Columns.Add(newColumn);

            if (i.Child is RowConstant)
            {
                (i.Child as RowConstant).AddValue(newColumn, this.adorner.outputs[0], now[0]);
            }
            else
            {
                RowConstant rc = new RowConstant(new string[] { newColumn }, new Domain.Domain[] { this.adorner.outputs[0] }, now);
                CrossJoin cj = new CrossJoin(i.Child, rc);
                i.NotifyChange(i.Child, cj);
            }
        }

        internal override void Expand(Update u, Command c)
        {
            if (!(this.tables.Contains(u.Table)) || this.insertOnly)
                return;

            // Attach the current value of the function to the insert statement
            u.Columns.Add(newColumn);

            if (u.Child is RowConstant)
            {
                (u.Child as RowConstant).AddValue(newColumn, this.adorner.outputs[0], now[0]);
            }
            else
            {
                RowConstant rc = new RowConstant(new string[] { newColumn }, new Domain.Domain[] { this.adorner.outputs[0] }, now);
                CrossJoin cj = new CrossJoin(u.Child, rc);
                u.NotifyChange(u.Child, cj);
            }
        }

        public override List<XElement> ToXElement()
        {
            XElement x = new XElement("Transform", new XAttribute("type", "Adorn"),
                new XAttribute("newColumn", newColumn),
                new XAttribute("insertOnly",insertOnly.ToString()),
                adorner.ToXElement());
            for (int i = 0; i < tables.Count; i++)
                x.Add(new XElement(tables[i]));

            List<XElement> xList = new List<XElement>();
            if (!(this.Child is SqlProvider))
                xList = ((Transform)this.Child).ToXElement();
            xList.Add(x);
            return xList;
        }
        new public static Adorn FromXElement(XElement x)
        {
            List<String> tables = new List<String>();
            String newCol = x.Attribute("newColumn").Value;
            foreach (var tab in x.Elements())
                tables.Add(tab.Name.LocalName);

            Function f = null;
            foreach (XElement xe in x.Elements())
            {
                if (xe.Name.LocalName == "Function" && xe.ToString() != "<Function />")
                {
                    switch (xe.Value)
                    {
                        case "GuavaData.CurrentTime":
                            f = new GuavaData.CurrentTime();
                            break;
                        case "GuavaData.CurrentMachine":
                            f = new GuavaData.CurrentMachine();
                            break;
                        case "GuavaData.CurrentUser":
                            f = new GuavaData.CurrentUser();
                            break;
                        default:
                            throw new Exception("Adorn: Error loading function from XML");
                    }
                }
            }
            
            bool onlyInsert = false;

            if (x.Attribute("insertOnly").Value == "true")
                onlyInsert = true;

            if (f != null && newCol != null && tables.Count > 0)
                return new Adorn(tables, newCol, f, onlyInsert);
            else
                throw new Exception("Error while loading Adorn from XML");
        }
    }
}
